using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using Microsoft.Data.Schema.Extensibility;
using Microsoft.Data.Schema.SchemaModel;
using Microsoft.Data.Schema.ScriptDom.Sql;
using Microsoft.Data.Schema.Sql.SchemaModel;
using Microsoft.Data.Schema.Sql;
using Microsoft.Data.Schema.StaticCodeAnalysis;
using System.IO;
using System.Text.RegularExpressions;
using System.Linq;


namespace Neznayka
{
    /// <summary>
    /// This is a SQL rule which returns a warning message 
    /// whenever it finds a variable name simliar to another variable name.
    /// </summary>

    [DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))]
    [DataRuleAttribute(
        NeznaykaConstants.NameSpace,
        NeznaykaConstants.VariableNameProximityRuleId,
        NeznaykaConstants.ResourceBaseName,
        NeznaykaConstants.VariableNameProximity_RuleName,
        NeznaykaConstants.CategoryVariableNaming,
        DescriptionResourceId = NeznaykaConstants.VariableNameProximity_ProblemDescription)]
    [SupportedElementType(typeof(ISqlFunction))]
    [SupportedElementType(typeof(ISqlTableValuedFunction))]
    [SupportedElementType(typeof(ISqlMultiStatementTableValuedFunction))]
    [SupportedElementType(typeof(ISqlProcedure))]
    [SupportedElementType(typeof(ISqlTrigger))]
    public class VariableNameProximityRule : StaticCodeAnalysisRule
    {
        #region Overrides
        /// <summary>
        /// Analyze the model element
        /// </summary>
        public override IList<DataRuleProblem> Analyze(DataRuleSetting ruleSetting, DataRuleExecutionContext context)
        {

            // (Re)-Load Environment settings
            List<DataRuleProblem> problems;
            SqlSchemaModel sqlSchemaModel;
            ISqlModelElement sqlElement;
            TSqlFragment sqlFragment;
            DMVRuleSetup.RuleSetup(context, out problems, out sqlSchemaModel, out sqlElement, out sqlFragment);

            // visitor to get the ocurrences of  Whiffy If statements
            VariableDeclarationVisitor visitor = new VariableDeclarationVisitor();
            sqlFragment.Accept(visitor);
            List<Literal> declaredVariableNames = visitor.VariableDeclarations;

            // Create problems for variable found with a single digit postpended.
            // Allow short names to have digits, there's less likelihood of confusing them
            // and we're restricting them to ints only anyway
            foreach (Literal declaredVariableName in declaredVariableNames)
            {
                string varName = declaredVariableName.Value;
                string lc_varName = varName.ToLower();
                //List<Literal> caseOnlyDuplicates = declaredVariableNames.FindAll(
                //    delegate( Literal item) {
                //        return item.Value.ToLower() == lc_varName;
                //    }
                // we don't want to use SqlComparer here.  If the variables are only different in case/accent
                // we must be using a sensitive collation - and the rule applies
                List<Literal> caseOnlyDuplicates = declaredVariableNames.FindAll( item => item.Value.ToLower() == lc_varName) ;
                if (caseOnlyDuplicates.Count > 1)
                {
                    DataRuleProblem problem = new DataRuleProblem(this,
                                                String.Format(CultureInfo.CurrentCulture, this.RuleProperties.Description, SqlRuleUtils.GetElementName(sqlSchemaModel, sqlElement)),
                                                sqlElement);

                    SqlRuleUtils.UpdateProblemPosition(problem, declaredVariableName.StartOffset, declaredVariableName.FragmentLength);
                    problems.Add(problem);
                }
//                else if (varName.Length >= 4)  // only use Levenshtein difference on long variable names
                else if (varName.Length >= DMVSettings.MinimumVariableNameLength)  // only use Levenshtein difference on long variable names
                {
                    //List<Literal> editdistanceDuplicates = declaredVariableNames.FindAll(
                    //    delegate(Literal item)
                    //    {
                    //        string lc_item = item.Value.ToLower() ;
                    //        return LevenshteinDistance.Compute(lc_varName, lc_item) <= 1;
                    //    }
                    //    );
                    List<Literal> editdistanceDuplicates = declaredVariableNames.FindAll(item => LevenshteinDistance.Compute(lc_varName, item.Value.ToLower()) <= 1 );
                    if (editdistanceDuplicates.Count > 1)
                    {
                        DataRuleProblem problem = new DataRuleProblem(this,
                                                    String.Format(CultureInfo.CurrentCulture, this.RuleProperties.Description, SqlRuleUtils.GetElementName(sqlSchemaModel, sqlElement)),
                                                    sqlElement);

                        SqlRuleUtils.UpdateProblemPosition(problem, declaredVariableName.StartOffset, declaredVariableName.FragmentLength);
                        problems.Add(problem);
                    }
                    else
                    {
//                        declaredVariableNames.Remove(declaredVariableName);
                    }
                }
            }

            return problems;
        }

        #endregion    
 
    }
}